;; Linux BPF CPU description  -*- Scheme -*-
;; Copyright (C) 2019 Free Software Foundation, Inc.
;;
;; Contributed by Oracle Inc.
;;
;; This file is part of the GNU Binutils and of GDB.
;;
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 3 of the
;; License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, write to the Free Software
;; Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
;; 02110-1301, USA.

;; This file contains a CGEN CPU description for the Linux kernel eBPF
;; instruction set.  eBPF is documented in the linux kernel source
;; tree.  See linux/Documentation/networking/filter.txt, and also the
;; sources in the networking subsystem, notably
;; linux/net/core/filter.c.

(include "simplify.inc")

(define-arch
  (name bpf)
  (comment "Linux kernel BPF")
  (insn-lsb0? #t)
  ;; XXX explain the default-alignment setting is for the simulator.
  ;; It is confusing that the simulator follows the emulated memory
  ;; access conventions for fetching instructions by pieces...
  (default-alignment unaligned)
  (machs bpf xbpf)
  (isas ebpfle ebpfbe xbpfle xbpfbe))

;;;; The ISAs

;; Logically, eBPF comforms a single instruction set featuring two
;; kind of instructions: 64-bit instructions and 128-bit instructions.
;;
;; The 64-bit instructions have the form:
;;
;;      code:8 regs:8 offset:16 imm:32
;;
;; Whereas the 128-bit instructions (at the moment there is only one
;; of such instructions, lddw) have the form:
;;
;;      code:8 regs:8 offset:16 imm:32 unused:32 imm:32 
;;
;; In both formats `regs' is itself composed by two fields:
;;
;;      dst:4 src:4
;;
;; The ISA is supposed to be orthogonal to endianness: the endianness
;; of the instruction fields follow the endianness of the host running
;; the eBPF program, and that's all.  However, this is not entirely
;; true.  The definition of an eBPF code in the Linux kernel is:
;;
;; struct bpf_insn {
;;	__u8	code;		/* opcode */
;;	__u8	dst_reg:4;	/* dest register */
;;	__u8	src_reg:4;	/* source register */
;;	__s16	off;		/* signed offset */
;;	__s32	imm;		/* signed immediate constant */
;; };
;;
;; Since the ordering of fields in C bitmaps is defined by the
;; implementation, the impact of endianness in the encoding of eBPF
;; instructions is effectively defined by GCC.  In particular, GCC
;; places dst_reg before src_reg in little-endian code, and the other
;; way around in big-endian code.
;;
;; So, in reality, eBPF comprises two instruction sets: one for
;; little-endian with instructions like:
;;
;;   code:8 src:4 dst:4 offset:16 imm:32 [unused:32 imm:32]
;;
;; and another for big-endian with instructions like:
;;
;;   code:8 dst:4 src:4 offset:16 imm:32 [unused:32 imm:32]
;;
;; where `offset' and the immediate fields are encoded in
;; little-endian and big-endian byte-order, respectively.

(define-pmacro (define-bpf-isa x-endian)
  (define-isa
    (name (.sym ebpf x-endian))
    (comment "The eBPF instruction set")
    ;; Default length to record in ifields.  This is used in
    ;; calculations involving bit numbers.
    (default-insn-word-bitsize 64)
    ;; Length of an unknown instruction.  Used by disassembly and by the
    ;; simulator's invalid insn handler.
    (default-insn-bitsize 64)
    ;; Number of bits of insn that can be initially fetched.  This is
    ;; the size of the smallest insn.
    (base-insn-bitsize 64)))

(define-bpf-isa le)
(define-bpf-isa be)

(define-pmacro (define-xbpf-isa x-endian)
  (define-isa
    (name (.sym xbpf x-endian))
    (comment "The xBPF instruction set")
    (default-insn-word-bitsize 64)
    (default-insn-bitsize 64)
    (base-insn-bitsize 64)))

(define-xbpf-isa le)
(define-xbpf-isa be)

(define-pmacro all-isas () (ISA ebpfle,ebpfbe,xbpfle,xbpfbe))
(define-pmacro xbpf-isas () (ISA xbpfle,xbpfbe))

(define-pmacro (endian-isas x-endian)
  ((ISA (.sym ebpf x-endian) (.sym xbpf x-endian))))

;;;; Hardware Hierarchy

;;
;;         bpf            architecture
;;          |
;;        bpfbf           cpu-family
;;      /       \
;;     bpf     xbpf       machine
;;      |       |
;;   bpf-def  xbpf-def    model

(define-cpu
  (name bpfbf)
  (comment "Linux kernel eBPF virtual CPU")
  (insn-endian big)
  (word-bitsize 64))

(define-mach
  (name bpf)
  (comment "Linux eBPF")
  (cpu bpfbf)
  (isas ebpfle ebpfbe))

(define-model
  (name bpf-def)
  (comment "Linux eBPF default model")
  (mach bpf)
  (unit u-exec "execution unit" ()
    1 ; issue
    1 ; done
    () ; state
    () ; inputs
    () ; outputs
    () ; profile action (default)
    ))

(define-mach
  (name xbpf)
  (comment "Experimental BPF")
  (cpu bpfbf)
  (isas ebpfle ebpfbe xbpfle xbpfbe))

(define-model
  (name xbpf-def)
  (comment "xBPF default model")
  (mach xbpf)
  (unit u-exec "execution unit" ()
    1 ; issue
    1 ; done
    () ; state
    () ; inputs
    () ; outputs
    () ; profile action (default)
    ))

;;;; Hardware Elements

;; eBPF programs can access 10 general-purpose registers which are
;; 64-bit.

(define-hardware
  (name h-gpr)
  (comment "General Purpose Registers")
  (attrs all-isas (MACH bpf xbpf))
  (type register DI (16))
  (indices keyword "%"
           ;; XXX the frame pointer fp is read-only, so it should
           ;; go in a different hardware.
           (;; ABI names.  Take priority when disassembling.
            (r0 0) (r1 1) (r2 2) (r3 3) (r4 4) (r5 5) (r6 6)
            (r7 7) (r8 8) (r9 9) (fp 10)
            ;; Additional names recognized when assembling.
            (r0 0) (r6 6) (r10 10))))

;; The program counter.  CGEN requires it, even if it is not visible
;; to eBPF programs.

(define-hardware
  (name h-pc)
  (comment "program counter")
  (attrs PC PROFILE all-isas)
  (type pc UDI)
  (get () (raw-reg h-pc))
  (set (newval) (set (raw-reg h-pc) newval)))
  
;; A 64-bit h-sint to be used by the imm64 operand below.  XXX this
;; shouldn't be needed, as h-sint is supposed to be able to hold
;; 64-bit values.  However, in practice CGEN limits h-sint to 32 bits
;; in 32-bit hosts.  To be fixed in CGEN.

(dnh h-sint64 "signed 64-bit integer" (all-isas) (immediate DI)
     () () ())

;;;; The Instruction Sets

;;; Fields and Opcodes

;; Convenience macro to shorten the definition of the fields below.
(define-pmacro (dwf x-name x-comment x-attrs
                    x-word-offset x-word-length x-start x-length
                    x-mode)
  "Define a field including its containing word."
  (define-ifield
    (name x-name)
    (comment x-comment)
    (.splice attrs (.unsplice x-attrs))
    (word-offset x-word-offset)
    (word-length x-word-length)
    (start x-start)
    (length x-length)
    (mode x-mode)))

;; For arithmetic and jump instructions the 8-bit code field is
;; subdivided in:
;;
;;  op-code:4 op-src:1 op-class:3

(dwf f-op-code "eBPF opcode code" (all-isas) 0 8 7 4 UINT)
(dwf f-op-src "eBPF opcode source" (all-isas) 0 8 3 1 UINT)
(dwf f-op-class "eBPF opcode instruction class" (all-isas) 0 8 2 3 UINT)

(define-normal-insn-enum insn-op-code-alu "eBPF instruction codes"
  (all-isas) OP_CODE_ f-op-code
  (;; Codes for OP_CLASS_ALU and OP_CLASS_ALU64
   (ADD #x0) (SUB #x1) (MUL #x2) (DIV #x3) (OR #x4) (AND #x5)
   (LSH #x6) (RSH #x7) (NEG #x8) (MOD #x9) (XOR #xa) (MOV #xb)
   (ARSH #xc) (END #xd)
   ;; xBPF-only: signed div, signed mod
   (SDIV #xe) (SMOD #xf)
   ;; Codes for OP_CLASS_JMP
   (JA #x0) (JEQ #x1) (JGT #x2) (JGE #x3) (JSET #x4)
   (JNE #x5) (JSGT #x6) (JSGE #x7) (CALL #x8) (EXIT #x9)
   (JLT #xa) (JLE #xb) (JSLT #xc) (JSLE #xd)))

(define-normal-insn-enum insn-op-src "eBPF instruction source"
  (all-isas) OP_SRC_ f-op-src
  ;; X => use `src' as source operand.
  ;; K => use `imm32' as source operand.
  ((K #b0) (X #b1)))

(define-normal-insn-enum insn-op-class "eBPF instruction class"
  (all-isas) OP_CLASS_ f-op-class
  ((LD    #b000) (LDX   #b001) (ST    #b010) (STX   #b011)
   (ALU   #b100) (JMP   #b101) (JMP32 #b110) (ALU64 #b111)))

;; For load/store instructions, the 8-bit code field is subdivided in:
;;
;; op-mode:3 op-size:2 op-class:3

(dwf f-op-mode "eBPF opcode mode" (all-isas) 0 8 7 3 UINT)
(dwf f-op-size "eBPF opcode size" (all-isas) 0 8 4 2 UINT)

(define-normal-insn-enum insn-op-mode "eBPF load/store instruction modes"
  (all-isas) OP_MODE_ f-op-mode
  ((IMM #b000) (ABS #b001) (IND #b010) (MEM #b011)
   ;; #b100 and #b101 are used in classic BPF only, reserved in eBPF.
   (XADD #b110)))

(define-normal-insn-enum insn-op-size "eBPF load/store instruction sizes"
  (all-isas) OP_SIZE_ f-op-size
  ((W  #b00)   ;; Word:        4 byte
   (H  #b01)   ;; Half-word:   2 byte
   (B  #b10)   ;; Byte:        1 byte
   (DW #b11))) ;; Double-word: 8 byte

;; The fields for the source and destination registers are a bit
;; tricky.  Due to the bizarre nibble swap between little-endian and
;; big-endian ISAs we need to keep different variants of the fields.
;;
;; Note that f-regs is used in the format spec of instructions that do
;; NOT use registers, where endianness is irrelevant i.e. f-regs is a
;; constant 0 opcode.

(dwf f-dstle "eBPF dst register field" ((ISA ebpfle xbpfle)) 8 8 3 4 UINT)
(dwf f-srcle "eBPF source register field" ((ISA ebpfle xbpfle)) 8 8 7 4 UINT)

(dwf f-dstbe "eBPF dst register field" ((ISA ebpfbe xbpfbe)) 8 8 7 4 UINT)
(dwf f-srcbe "eBPF source register field" ((ISA ebpfbe xbpfbe)) 8 8 3 4 UINT)

(dwf f-regs "eBPF registers field" (all-isas) 8 8 7 8 UINT)

;; Finally, the fields for the immediates.
;;
;; The 16-bit offsets and 32-bit immediates do not present any special
;; difficulty: we put them in their own instruction word so the
;; byte-endianness will be properly applied.

(dwf f-offset16 "eBPF offset field" (all-isas) 16 16 15 16 HI)
(dwf f-imm32 "eBPF 32-bit immediate field" (all-isas) 32 32 31 32 INT)

;; For the disjoint 64-bit signed immediate, however, we need to use a
;; multi-ifield.

(dwf f-imm64-a "eBPF 64-bit immediate a" (all-isas) 32 32 31 32 UINT)
(dwf f-imm64-b "eBPF 64-bit immediate b" (all-isas) 64 32 31 32 UINT)
(dwf f-imm64-c "eBPF 64-bit immediate c" (all-isas) 96 32 31 32 UINT)

(define-multi-ifield
  (name f-imm64)
  (comment "eBPF 64-bit immediate field")
  (attrs all-isas)
  (mode DI)
  (subfields f-imm64-a f-imm64-b f-imm64-c)
  (insert (sequence ()
                    (set (ifield f-imm64-b) (const 0))
                    (set (ifield f-imm64-c) (srl (ifield f-imm64) (const 32)))
                    (set (ifield f-imm64-a) (and (ifield f-imm64) (const #xffffffff)))))
  (extract (sequence ()
                     (set (ifield f-imm64)
                          (or (sll UDI (zext UDI (ifield f-imm64-c)) (const 32))
                              (zext UDI (ifield f-imm64-a)))))))

;;; Operands

;; A couple of source and destination register operands are defined
;; for each ISA: ebpfle and ebpfbe.

(dno dstle "destination register" ((ISA ebpfle xbpfle)) h-gpr f-dstle)
(dno srcle "source register" ((ISA ebpfle xbpfle)) h-gpr f-srcle)

(dno dstbe "destination register" ((ISA ebpfbe xbpfbe)) h-gpr f-dstbe)
(dno srcbe "source register" ((ISA ebpfbe xbpfbe)) h-gpr f-srcbe)

;; Jump instructions have a 16-bit PC-relative address.
;; CALL instructions have a 32-bit PC-relative address.

(dno disp16 "16-bit PC-relative address" (all-isas PCREL-ADDR) h-sint
     f-offset16)
(dno disp32 "32-bit PC-relative address" (all-isas PCREL-ADDR) h-sint
     f-imm32)

;; Immediate operands in eBPF are signed, and we want the disassembler
;; to print negative values in a sane way.  Therefore we use the macro
;; below to register a printer, which is itself defined as a C
;; function in bpf.opc.

;; define-normal-signed-immediate-operand
(define-pmacro (dnsio x-name x-comment x-attrs x-type x-index)
  (define-operand
    (name x-name)
    (comment x-comment)
    (.splice attrs (.unsplice x-attrs))
    (type x-type)
    (index x-index)
    (handlers (print "immediate"))))

(dnsio imm32 "32-bit immediate" (all-isas) h-sint f-imm32)
(dnsio offset16 "16-bit offset" (all-isas) h-sint f-offset16)

;; The 64-bit immediate cannot use the default
;; cgen_parse_signed_integer, because it assumes operands are at much
;; 32-bit wide.  Use our own.

(define-operand
  (name imm64)
  (comment "64-bit immediate")
  (attrs all-isas)
  (type h-sint64)
  (index f-imm64)
  (handlers (parse "imm64") (print "immediate")))

;; The endle/endbe instructions take an operand to specify the word
;; width in endianness conversions.  We use both a parser and printer,
;; which are defined as C functions in bpf.opc.

(define-operand
  (name endsize)
  (comment "endianness size immediate: 16, 32 or 64")
  (attrs all-isas)
  (type h-uint)
  (index f-imm32)
  (handlers (parse "endsize") (print "endsize")))

;;; ALU instructions

;; For each opcode in insn-op-code-alu representing and integer
;; arithmetic instruction (ADD, SUB, etc) we define a bunch of
;; instruction variants:
;;
;;   ADD[32]{i,r}le for the little-endian ISA
;;   ADD[32]{i,r}be for the big-endian ISA
;;
;; The `i' variants perform `dst OP imm32 -> dst' operations.
;; The `r' variants perform `dst OP src -> dst' operations.
;;
;; The variants with 32 in their name are of ALU class.  Otherwise
;; they are ALU64 class.

(define-pmacro (define-alu-insn-un x-basename x-suffix x-op-class x-op-code
                 x-endian x-mode x-semop)
  (dni (.sym x-basename x-suffix x-endian)
       (.str x-basename x-suffix)
       (endian-isas x-endian)
       (.str x-basename x-suffix " $dst" x-endian)
       (+ (f-imm32 0) (f-offset16 0) ((.sym f-src x-endian) 0) (.sym dst x-endian)
          x-op-class OP_SRC_K x-op-code)
       (set x-mode (.sym dst x-endian) (x-semop x-mode (.sym dst x-endian)))
       ()))

(define-pmacro (define-alu-insn-bin x-basename x-suffix x-op-class x-op-code
                 x-endian x-mode x-semop x-isas)
  (begin
    ;; dst = dst OP immediate
    (dni (.sym x-basename x-suffix "i" x-endian)
         (.str x-basename x-suffix " immediate")
         (.splice (.unsplice x-isas))
         (.str x-basename x-suffix " $dst" x-endian ",$imm32")
         (+ imm32 (f-offset16 0) ((.sym f-src x-endian) 0) (.sym dst x-endian)
            x-op-class OP_SRC_K x-op-code)
         (set x-mode (.sym dst x-endian) (x-semop x-mode (.sym dst x-endian) imm32))
         ())
    ;; dst = dst OP src
    (dni (.sym x-basename x-suffix "r" x-endian)
         (.str x-basename x-suffix " register")
         (.splice (.unsplice x-isas))
         (.str x-basename x-suffix " $dst" x-endian ",$src" x-endian)
         (+ (f-imm32 0) (f-offset16 0) (.sym src x-endian) (.sym dst x-endian)
            x-op-class OP_SRC_X x-op-code)
         (set x-mode (.sym dst x-endian)
                      (x-semop x-mode (.sym dst x-endian) (.sym src x-endian)))
         ())))

(define-pmacro (define-alu-insn-mov x-basename x-suffix x-op-class x-op-code
                 x-endian x-mode)
  (begin
    (dni (.sym mov x-suffix "i" x-endian)
         (.str mov x-suffix " immediate")
         (endian-isas x-endian)
         (.str x-basename x-suffix " $dst" x-endian ",$imm32")
         (+ imm32 (f-offset16 0) ((.sym f-src x-endian) 0) (.sym dst x-endian)
            x-op-class OP_SRC_K x-op-code)
         (set x-mode (.sym dst x-endian) imm32)
         ())
    (dni (.sym mov x-suffix "r" x-endian)
         (.str mov x-suffix " register")
         (endian-isas x-endian)
         (.str x-basename x-suffix " $dst" x-endian ",$src" x-endian)
         (+ (f-imm32 0) (f-offset16 0) (.sym src x-endian) (.sym dst x-endian)
            x-op-class OP_SRC_X x-op-code)
         (set x-mode (.sym dst x-endian) (.sym src x-endian))
         ())))


;; Unary ALU instructions (neg)
(define-pmacro (daiu x-basename x-op-code x-endian x-semop)
  (begin
    (define-alu-insn-un x-basename "" OP_CLASS_ALU64 x-op-code x-endian DI x-semop)
    (define-alu-insn-un x-basename "32" OP_CLASS_ALU x-op-code x-endian USI x-semop)))

;; Binary ALU instructions (all the others)
;; For ALU32: DST = (u32) DST OP (u32) SRC is correct semantics
(define-pmacro (daib x-basename x-op-code x-endian x-semop x-isas)
  (begin
    (define-alu-insn-bin x-basename "" OP_CLASS_ALU64 x-op-code x-endian DI x-semop x-isas)
    (define-alu-insn-bin x-basename "32" OP_CLASS_ALU x-op-code x-endian USI x-semop x-isas)))

;; Move ALU instructions (mov)
(define-pmacro (daim x-basename x-op-code x-endian)
  (begin
    (define-alu-insn-mov x-basename "" OP_CLASS_ALU64 x-op-code x-endian DI)
    (define-alu-insn-mov x-basename "32" OP_CLASS_ALU x-op-code x-endian USI)))

(define-pmacro (define-alu-instructions x-endian)
  (begin
    (daib add OP_CODE_ADD x-endian add (endian-isas x-endian))
    (daib sub OP_CODE_SUB x-endian sub (endian-isas x-endian))
    (daib mul OP_CODE_MUL x-endian mul (endian-isas x-endian))
    (daib div OP_CODE_DIV x-endian udiv (endian-isas x-endian))
    (daib or  OP_CODE_OR x-endian or (endian-isas x-endian))
    (daib and OP_CODE_AND x-endian and (endian-isas x-endian))
    (daib lsh OP_CODE_LSH x-endian sll (endian-isas x-endian))
    (daib rsh OP_CODE_RSH x-endian srl (endian-isas x-endian))
    (daib mod OP_CODE_MOD x-endian umod (endian-isas x-endian))
    (daib xor OP_CODE_XOR x-endian xor (endian-isas x-endian))
    (daib arsh OP_CODE_ARSH x-endian sra (endian-isas x-endian))
    (daib sdiv OP_CODE_SDIV x-endian div ((ISA (.sym xbpf x-endian))))
    (daib smod OP_CODE_SMOD x-endian mod ((ISA (.sym xbpf x-endian))))
    (daiu neg OP_CODE_NEG x-endian neg)
    (daim mov OP_CODE_MOV x-endian)))

(define-alu-instructions le)
(define-alu-instructions be)

;;; Endianness conversion instructions

;; The endianness conversion instructions come in several variants:
;;
;;  END{le,be}le for the little-endian ISA
;;  END{le,be}be for the big-endian ISA
;;
;; Please do not be confused by the repeated `be' and `le' here.  Each
;; ISA has both endle and endbe instructions.  It is the disposition
;; of the source and destination register fields that change between
;; ISAs, not the semantics of the instructions themselves (see section
;; "The ISAs" above in this very file.)

(define-pmacro (define-endian-insn x-suffix x-op-src x-endian)
  (dni (.sym "end" x-suffix x-endian)
       (.str "end" x-suffix " register")
       (endian-isas x-endian)
       (.str "end" x-suffix " $dst" x-endian ",$endsize")
       (+  (f-offset16 0) ((.sym f-src x-endian) 0) (.sym dst x-endian) endsize
           OP_CLASS_ALU x-op-src OP_CODE_END)
       (set (.sym dst x-endian)
            (c-call DI (.str "bpfbf_end" x-suffix) (.sym dst x-endian) endsize))
       ()))

(define-endian-insn "le" OP_SRC_K le)
(define-endian-insn "be" OP_SRC_X le)
(define-endian-insn "le" OP_SRC_K be)
(define-endian-insn "be" OP_SRC_X be)

;;; Load/Store instructions

;; The lddw instruction takes a 64-bit immediate as an operand.  Since
;; this instruction also takes a `dst' operand, we need to define a
;; variant for each ISA:
;;
;;  LDDWle for the little-endian ISA
;;  LDDWbe for the big-endian ISA  

(define-pmacro (define-lddw x-endian)
  (dni (.sym lddw x-endian)
       (.str "lddw" x-endian)
       (endian-isas x-endian)
       (.str "lddw $dst" x-endian ",$imm64")
       (+ imm64 (f-offset16 0) ((.sym f-src x-endian) 0)
          (.sym dst x-endian)
          OP_CLASS_LD OP_SIZE_DW OP_MODE_IMM)
       (set DI (.sym dst x-endian) imm64)
       ()))

(define-lddw le)
(define-lddw be)

;; The absolute load instructions are non-generic loads designed to be
;; used in socket filters.  They come in several variants:
;;
;; LDABS{w,h,b,dw}

(define-pmacro (dlabs x-suffix x-size x-smode)
  (dni (.sym "ldabs" x-suffix)
       (.str "ldabs" x-suffix)
       (all-isas)
       (.str "ldabs" x-suffix " $imm32")
       (+ imm32 (f-offset16 0) (f-regs 0)
          OP_CLASS_LD OP_MODE_ABS (.sym OP_SIZE_ x-size))
       (set x-smode
            (reg x-smode h-gpr 0)
            (mem x-smode
                 (add DI
                      (mem DI
                           (add DI
                                (reg DI h-gpr 6) ;; Pointer to struct sk_buff
                                (c-call "bpfbf_skb_data_offset")))
                      imm32)))
       ;; XXX this clobbers R1-R5
       ()))

(dlabs "w" W SI)
(dlabs "h" H HI)
(dlabs "b" B QI)
(dlabs "dw" DW DI)

;; The indirect load instructions are non-generic loads designed to be
;; used in socket filters.  They come in several variants:
;;
;; LDIND{w,h,b,dw}le for the little-endian ISA
;; LDIND[w,h,b,dw}be for the big-endian ISA

(define-pmacro (dlind x-suffix x-size x-endian x-smode)
  (dni (.sym "ldind" x-suffix x-endian)
       (.str "ldind" x-suffix)
       (endian-isas x-endian)
       (.str "ldind" x-suffix " $src" x-endian ",$imm32")
       (+ imm32 (f-offset16 0) ((.sym f-dst x-endian) 0) (.sym src x-endian)
          OP_CLASS_LD OP_MODE_IND (.sym OP_SIZE_ x-size))
       (set x-smode
            (reg x-smode h-gpr 0)
            (mem x-smode
                 (add DI
                      (mem DI
                           (add DI
                                (reg DI h-gpr 6) ;; Pointer to struct sk_buff
                                (c-call "bpfbf_skb_data_offset")))
                      (add DI
                           (.sym src x-endian)
                           imm32))))
       ;; XXX this clobbers R1-R5
       ()))

(define-pmacro (define-ldind x-endian)
  (begin    
    (dlind "w" W x-endian SI)
    (dlind "h" H x-endian HI)
    (dlind "b" B x-endian QI)
    (dlind "dw" DW x-endian DI)))

(define-ldind le)
(define-ldind be)

;; Generic load and store instructions are provided for several word
;; sizes.  They come in several variants:
;;
;;  LDX{b,h,w,dw}le, STX{b,h,w,dw}le for the little-endian ISA
;;
;;  LDX{b,h,w,dw}be, STX{b,h,w,dw}be for the big-endian ISA
;;
;; Loads operate on [$SRC+-OFFSET] -> $DST
;; Stores operate on $SRC -> [$DST+-OFFSET]

(define-pmacro (dxli x-basename x-suffix x-size x-endian x-mode)
  (dni (.sym x-basename x-suffix x-endian)
       (.str x-basename x-suffix)
       (endian-isas x-endian)
       (.str x-basename x-suffix " $dst" x-endian ",[$src" x-endian "+$offset16]")
       (+ (f-imm32 0) offset16 (.sym src x-endian) (.sym dst x-endian)
          OP_CLASS_LDX (.sym OP_SIZE_ x-size) OP_MODE_MEM)
       (set x-mode
            (.sym dst x-endian)
            (mem x-mode (add DI (.sym src x-endian) offset16)))
       ()))

(define-pmacro (dxsi x-basename x-suffix x-size x-endian x-mode)
  (dni (.sym x-basename x-suffix x-endian)
       (.str x-basename x-suffix)
       (endian-isas x-endian)
       (.str x-basename x-suffix " [$dst" x-endian "+$offset16],$src" x-endian)
       (+ (f-imm32 0) offset16 (.sym src x-endian) (.sym dst x-endian)
          OP_CLASS_STX (.sym OP_SIZE_ x-size) OP_MODE_MEM)
       (set x-mode
            (mem x-mode (add DI (.sym dst x-endian) offset16))
            (.sym src x-endian)) ;; XXX address is section-relative
       ()))

(define-pmacro (define-ldstx-insns x-endian)
  (begin
    (dxli "ldx" "w" W x-endian SI)
    (dxli "ldx" "h" H x-endian HI)
    (dxli "ldx" "b" B x-endian QI)
    (dxli "ldx" "dw" DW x-endian DI)

    (dxsi "stx" "w" W x-endian SI)
    (dxsi "stx" "h" H x-endian HI)
    (dxsi "stx" "b" B x-endian QI)
    (dxsi "stx" "dw" DW x-endian DI)))

(define-ldstx-insns le)
(define-ldstx-insns be)

;; Generic store instructions of the form IMM32 -> [$DST+OFFSET] are
;; provided in several variants:
;;
;;  ST{b,h,w,dw}le for the little-endian ISA
;;  ST{b,h,w,dw}be for the big-endian ISA

(define-pmacro (dsti x-suffix x-size x-endian x-mode)
  (dni (.sym "st" x-suffix x-endian)
       (.str "st" x-suffix)
       (endian-isas x-endian)
       (.str "st" x-suffix " [$dst" x-endian "+$offset16],$imm32")
       (+ imm32 offset16 ((.sym f-src x-endian) 0) (.sym dst x-endian)
          OP_CLASS_ST (.sym OP_SIZE_ x-size) OP_MODE_MEM)
       (set x-mode
            (mem x-mode (add DI (.sym dst x-endian) offset16))
            imm32) ;; XXX address is section-relative
       ()))

(define-pmacro (define-st-insns x-endian)
  (begin
    (dsti "b" B x-endian QI)
    (dsti "h" H x-endian HI)
    (dsti "w" W x-endian SI)
    (dsti "dw" DW x-endian DI)))

(define-st-insns le)
(define-st-insns be)

;;; Jump instructions

;; Compare-and-jump instructions, on the other hand, make use of
;; registers.  Therefore, we need to define several variants in both
;; ISAs:
;;
;;   J{eq,gt,ge,lt,le,set,ne,sgt,sge,slt,sle}[32]{i,r}le for the
;;   little-endian ISA.
;;   J{eq,gt,ge,lt,le,set,ne.sgt,sge,slt,sle}[32]{i,r}be for the
;;   big-endian ISA.

(define-pmacro (define-cond-jump-insn x-cond x-suffix x-op-class x-op-code x-endian x-mode x-semop)
  (begin
    (dni (.sym j x-cond x-suffix i x-endian)
         (.str j x-cond x-suffix " i")
         (endian-isas x-endian)
         (.str "j" x-cond x-suffix " $dst" x-endian ",$imm32,$disp16")
         (+ imm32 disp16 ((.sym f-src x-endian) 0) (.sym dst x-endian)
            x-op-class OP_SRC_K (.sym OP_CODE_ x-op-code))
         (if VOID (x-semop x-mode (.sym dst x-endian) imm32)
             (set DI
                  (reg DI h-pc) (add DI (reg DI h-pc)
                                     (mul DI (add HI disp16 1) 8))))
         ())
    (dni (.sym j x-cond x-suffix r x-endian)
         (.str j x-cond x-suffix " r")
         (endian-isas x-endian)
         (.str "j" x-cond x-suffix " $dst" x-endian ",$src" x-endian ",$disp16")
         (+ (f-imm32 0) disp16 (.sym src x-endian) (.sym dst x-endian)
            x-op-class OP_SRC_X (.sym OP_CODE_ x-op-code))
         (if VOID (x-semop x-mode (.sym dst x-endian) (.sym src x-endian))
             (set DI
                  (reg DI h-pc) (add DI (reg DI h-pc)
                                     (mul DI (add HI disp16 1) 8))))
         ())))

(define-pmacro (dcji x-cond x-op-code x-endian x-semop)
  (begin
    (define-cond-jump-insn x-cond "" OP_CLASS_JMP x-op-code x-endian DI x-semop)
    (define-cond-jump-insn x-cond "32" OP_CLASS_JMP32 x-op-code x-endian SI x-semop )))

(define-pmacro (define-condjump-insns x-endian)
  (begin
    (dcji "eq" JEQ x-endian eq)
    (dcji "gt" JGT x-endian gtu)
    (dcji "ge" JGE x-endian geu)
    (dcji "lt" JLT x-endian ltu)
    (dcji "le" JLE x-endian leu)
    (dcji "set" JSET x-endian and)
    (dcji "ne" JNE x-endian ne)
    (dcji "sgt" JSGT x-endian gt)
    (dcji "sge" JSGE x-endian ge)
    (dcji "slt" JSLT x-endian lt)
    (dcji "sle" JSLE x-endian le)))

(define-condjump-insns le)
(define-condjump-insns be)

;; The `call' instruction doesn't make use of registers, but the
;; semantic routine should have access to the src register in order to
;; properly interpret the meaning of disp32.  Therefore we need one
;; version per ISA.

(define-pmacro (define-call-insn x-endian)
  (dni (.sym call x-endian)
       "call"
       (endian-isas x-endian)
       "call $disp32"
       (+ disp32 (f-offset16 0) (.sym src x-endian) ((.sym f-dst x-endian) 0)
          OP_CLASS_JMP OP_SRC_K OP_CODE_CALL)
       (c-call VOID
               "bpfbf_call" disp32 (ifield (.sym f-src x-endian)))
       ()))

(define-call-insn le)
(define-call-insn be)

(define-pmacro (define-callr-insn x-endian)
  (dni (.sym callr x-endian)
       "callr"
       ((ISA (.sym xbpf x-endian)))
       (.str "call $dst" x-endian)
       (+ (f-imm32 0) (f-offset16 0) ((.sym f-src x-endian) 0) (.sym dst x-endian)
          OP_CLASS_JMP OP_SRC_X OP_CODE_CALL)
       (c-call VOID
               "bpfbf_callr" (ifield (.sym f-dst x-endian)))
       ()))

(define-callr-insn le)
(define-callr-insn be)

;; The jump-always and `exit' instructions dont make use of either
;; source nor destination registers, so only one variant per
;; instruction is defined.

(dni ja "ja" (all-isas) "ja $disp16"
     (+ (f-imm32 0) disp16 (f-regs 0)
        OP_CLASS_JMP OP_SRC_K OP_CODE_JA)
     (set DI (reg DI h-pc) (add DI (reg DI h-pc)
                                (mul DI (add HI disp16 1) 8)))
     ())

(dni "exit" "exit" (all-isas) "exit"
     (+ (f-imm32 0) (f-offset16 0) (f-regs 0)
        OP_CLASS_JMP (f-op-src 0) OP_CODE_EXIT)
     (c-call VOID "bpfbf_exit")
     ())

;;; Atomic instructions

;; The atomic exchange-and-add instructions come in two flavors: one
;; for swapping 64-bit quantities and another for 32-bit quantities.

(define-pmacro (sem-exchange-and-add x-endian x-mode)
  (sequence VOID ((x-mode tmp))
            ;; XXX acquire lock in simulator...  as a hardware element?
            (set x-mode tmp (mem x-mode (add DI (.sym dst x-endian) offset16)))
            (set x-mode
                 (mem x-mode (add DI (.sym dst x-endian) offset16))
                 (add x-mode tmp (.sym src x-endian)))))

(define-pmacro (define-atomic-insns x-endian)
  (begin
    (dni (.str "xadddw" x-endian)
         "xadddw"
         (endian-isas x-endian)
         (.str "xadddw [$dst" x-endian "+$offset16],$src" x-endian)
         (+ (f-imm32 0) (.sym src x-endian) (.sym dst x-endian)
            offset16 OP_MODE_XADD OP_SIZE_DW OP_CLASS_STX)
         (sem-exchange-and-add x-endian DI)
         ())
    (dni (.str "xaddw" x-endian)
         "xaddw"
         (endian-isas x-endian)
         (.str "xaddw [$dst" x-endian "+$offset16],$src" x-endian)
         (+ (f-imm32 0) (.sym src x-endian) (.sym dst x-endian)
            offset16 OP_MODE_XADD OP_SIZE_W OP_CLASS_STX)
         (sem-exchange-and-add x-endian SI)
         ())))

(define-atomic-insns le)
(define-atomic-insns be)

;;; Breakpoint instruction

;; The brkpt instruction is used by the BPF simulator and it doesn't
;; really belong to the eBPF instruction set.

(dni "brkpt" "brkpt" (all-isas)  "brkpt"
     (+ (f-imm32 0) (f-offset16 0) (f-regs 0)
        OP_CLASS_ALU OP_SRC_X OP_CODE_NEG)
     (c-call VOID "bpfbf_breakpoint")
     ())
